
#include "uisystem.h"
#include "gamemanager.h"
#include "resourcemanager.h"

#include "cocos-ext.h"

USING_NS_CC_EXT;

using namespace std;
using namespace tinyxml2;

UISystem* UISystem::_instance = nullptr;

UISystem* UISystem::getInstance() {
	if(!_instance) {
		_instance = new UISystem();
	}

	return _instance;
}

UISystem::UISystem(void){

}

UISystem::~UISystem(void){
	CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

void UISystem::init() {
	loadScaleButtonDefinitions();
	
	CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, -99, true);
}

void UISystem::changLayout(CCNode* parent, const char* layoutFile) {
    removeTopLayout();

	_mapNodeMapping.clear();
	_currentLayoutName = layoutFile;
	addLayout(parent, layoutFile);
}

void UISystem::addLayout(CCNode* parent, const char* layoutFile) {
	CCLayer* layer = CCLayer::create();
	CCSize size = parent->getContentSize();
	layer->setContentSize(size);
	layer->setPosition(CCPointZero);
	layer->setAnchorPoint(CCPointZero);
	parent->addChild(layer);

	_layoutStack.push(layer);

	unsigned long length = 0;
	char* str = (char*)CCFileUtils::sharedFileUtils()->getFileData(layoutFile, "rb", &length);
	string xml = str;
	delete[] str;

	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	XMLElement* root = doc.FirstChildElement("layout");
	parseLayout(layer, root);
}

void UISystem::removeTopLayout() {
    if(!_layoutStack.empty()) {
        CCLayer* layer = _layoutStack.top();
        _mapEventHandlers.erase(layer->m_uID);
        _mapNodeCreationHandlers.erase(layer->m_uID);

        layer->removeFromParent();
        _layoutStack.pop();
    }
}

bool UISystem::ccTouchBegan(CCTouch* touch, CCEvent* event) {
	if(_layoutStack.empty()) {
		return false;
	}

	CCLayer* layer = _layoutStack.top();
	auto id = getTouchedNode(touch->getLocation(), layer);

	if(id > 0) {
		string s = _mapNodeMapping[id];
		auto it = _mapEventHandlers.find(layer->m_uID);
        if(it != _mapEventHandlers.end() && it->second) {
			auto func = it->second;
            func(cref(s));
		}
		
		return true;
	}
	
	return false;
}

unsigned int UISystem::getTouchedNode(const CCPoint& pt, CCNode* parent) {
	unsigned int ret = 0;

	CCArray* arr = parent->getChildren();
	auto count = parent->getChildrenCount();
	for(unsigned int i = 0; i < count; i++) {
		CCNode* node = (CCNode*)arr->objectAtIndex(i);
		auto id = getTouchedNode(pt, node);
		if(id > 0) {
			ret = id;
			break;
		}

		if(node->hitTest(pt)) {
			auto nodeID = node->m_uID;
			if(_mapNodeMapping.find(nodeID) != _mapNodeMapping.end()) {
				ret = node->m_uID;
			}
		}
	}
	
	return ret;
}

void UISystem::ccTouchMoved(CCTouch* touch, CCEvent* event) {

}

void UISystem::ccTouchEnded(CCTouch* touch, CCEvent* event) {

}

void UISystem::ccTouchCancelled(CCTouch* touch, CCEvent* event) {

}

void UISystem::parseLayout(CCNode* parentNode, XMLElement* parentItem) {
    auto it = _mapNodeCreationHandlers.find(_layoutStack.top()->m_uID);
    NodeCreationHandlingFunction func;
    if(it != _mapNodeCreationHandlers.end()) {
        func = it->second;
    }

	XMLElement* item = parentItem->FirstChildElement("i");
    while(item){
		string key = item->Attribute("k");
		string type = item->Attribute("t");
		CCRect rect = CCRectMake(
									item->IntAttribute("x"),
									item->IntAttribute("y"),
									item->IntAttribute("w"),
									item->IntAttribute("h"));

		CCNode* node = nullptr;
		if(type == "text") {
			bool shouldSkip = false;
            if(!key.empty() && func) {
                int createdNodeID = func(parentNode, key, rect);
                shouldSkip = (createdNodeID > 0);
                if(shouldSkip) {
                    _mapNodeMapping.insert(make_pair(createdNodeID, key));
                }
            }

			if(!shouldSkip) {
				node = createLabel(parentNode, key, rect, item);
			}
		} else if(type == "bitmap") {
			string res = item->Attribute("res");
			if(res == "bg_common.jpg" ) {
				CCSprite* sprBg = MAKE_SPRITE("ui_bg.jpg");
				sprBg->setPosition(ccp(parentNode->getContentSize().width / 2.0f, parentNode->getContentSize().height / 2.0f));
				sprBg->setScaleX(parentNode->getContentSize().width / sprBg->getContentSize().width);
				sprBg->setScaleY(parentNode->getContentSize().height/ sprBg->getContentSize().height);
				parentNode->addChild(sprBg, -1);
			} else {
				bool shouldSkip = false;
				if(!key.empty() && !func) {
					int createdNodeID = func(parentNode, key, rect);
                    shouldSkip = (createdNodeID > 0);
                    if(shouldSkip) {
                        _mapNodeMapping.insert(make_pair(createdNodeID, key));
                    }
				}
				
				if(!shouldSkip) {
					node = createSprite(parentNode, key, rect, item);
				}
			}
		} else {
			string res = item->Attribute("res");
			bool shouldSkip = false;
			if(!key.empty() && func) {
				int createdNodeID = func(parentNode, key, rect);
                shouldSkip = (createdNodeID > 0);
                if(shouldSkip) {
                    _mapNodeMapping.insert(make_pair(createdNodeID, key));
                }
			}

			if(!shouldSkip) {
				if(res.size() > 6 && res.substr(0, 6) == "scale9") {
					node = createScale9Sprite(parentNode, key, rect, item);
				} else {
					node = createSprite(parentNode, key, rect, item);
				}
			}
		}

		if(node) {
			parseLayout(node, item);
		}

		item = item->NextSiblingElement("i");
	}
}

CCNode* UISystem::createLabel(CCNode* parentNode, const string& key, const CCRect& rect, XMLElement* item) {
	CCTextAlignment alignment = kCCTextAlignmentLeft;
	const char* str = item->Attribute("alignment");
	if(strcmp(str, "center") == 0) {
		alignment = kCCTextAlignmentCenter;
	} else if(strcmp(str, "right") == 0) {
		alignment = kCCTextAlignmentRight;
	}

	string strColor = item->Attribute("color");
	string part = strColor.substr(1,2);
	part = "0x" + part;
	GLubyte r = static_cast<GLubyte>(std::strtol(part.c_str(), nullptr, 16));
	part = strColor.substr(3,2);
	part = "0x" + part;
	GLubyte g = static_cast<GLubyte>(std::strtol(part.c_str(), nullptr, 16));
	part = strColor.substr(5,2);
	part = "0x" + part;
	GLubyte b = static_cast<GLubyte>(std::strtol(part.c_str(), nullptr, 16));
	ccColor3B color = ccc3(r,g,b);

	CCLabelTTF* label = CCLabelTTF::create(item->Attribute("title"), "arial", static_cast<float>(atof(item->Attribute("size"))), rect.size, alignment);
    if(_layoutStack.top() == parentNode) {
        label->setPosition(SCALE_PT(rect.origin));
        label->setScale(GM->minscale);
    } else {
	    label->setPosition(rect.origin);
    }
	label->setColor(color);
	parentNode->addChild(label);

	if(!key.empty()) {
		_mapNodeMapping.insert(make_pair(label->m_uID, key));
	}

	return label;
}

CCNode* UISystem::createSprite(CCNode* parentNode, const string& key, const CCRect& rect, tinyxml2::XMLElement* item) {
	string res = item->Attribute("res");
	if(res.substr(res.size() - 4, 4) != ".png") {
		res += ".png";
	}
	CCSprite* spr = MAKE_SPRITE(res.c_str());
	CCSize size = spr->getContentSize();
    if(_layoutStack.top() == parentNode) {
        spr->setScaleX(rect.size.width / size.width * GM->minscale);
        spr->setScaleY(rect.size.height / size.height * GM->minscale);
        spr->setPosition(SCALE_PT(rect.origin));
    } else {
        spr->setScaleX(rect.size.width / size.width);
        spr->setScaleY(rect.size.height / size.height);
        spr->setPosition(rect.origin);
    }

	spr->setScaleX(rect.size.width / size.width);
	spr->setScaleY(rect.size.height / size.height);
	spr->setPosition(rect.origin);

	_mapNodeMapping.insert(make_pair(spr->m_uID, key));

	parentNode->addChild(spr);

	return spr;
}

CCNode* UISystem::createScale9Sprite(CCNode* parentNode, const string& key, const CCRect& rect, XMLElement* item) {
	CCRect insetCaps = _mapScaleButtons[item->Attribute("res")];
	string res = "public/resource/";
	res += item->Attribute("res");
	res += ".png";
	CCScale9Sprite* spr = CCScale9Sprite::create(insetCaps, res.c_str());
    spr->setPreferredSize(rect.size);
    if(_layoutStack.top() == parentNode) {
        spr->setPosition(SCALE_PT(rect.origin));
        spr->setScale(GM->minscale);
    } else {
        spr->setPosition(rect.origin);
    }

	_mapNodeMapping.insert(make_pair(spr->m_uID, key));

	parentNode->addChild(spr);

	return spr;
}

void UISystem::loadScaleButtonDefinitions() {
	_mapScaleButtons.clear();

	unsigned long length = 0;
	char* str = (char*)CCFileUtils::sharedFileUtils()->getFileData("public/config/ui/uielements.xml", "rb", &length);
	string xml = str;
	delete[] str;
    
    tinyxml2::XMLDocument doc;
    doc.Parse(xml.c_str());
    
    XMLElement* root = doc.FirstChildElement("items");

    XMLElement* item = root->FirstChildElement("i");
    while(item){
		string key = item->Attribute("k");
		CCRect rect = CCRectMake(
									item->IntAttribute("x"),
									item->IntAttribute("y"),
									item->IntAttribute("w"),
									item->IntAttribute("h"));

		_mapScaleButtons.insert(make_pair(key, rect));

		item = item->NextSiblingElement("i");
	}
}


void UISystem::registerTouchHandler(int id, function<void (string const &)> func) {
    _mapEventHandlers[id] = func;
}

void UISystem::registerNodeCreationHandler(int id, function<int (CCNode *, string const &, CCRect const &)> func) {
    _mapNodeCreationHandlers[id] = func;
}
